Developing a Word Task Pane App
“The ability to communicate effectively is paramount to success.” – Ty Anderson
Some people quote famous people to give their article a bit more weight. Me? I like to quote myself. And why not? How do you think big like Mark Twain got to be some quotable? I can assure it wasn’t by quoting other people.
Anyway, my quote is directly related to today’s topic… building an “Apps for Office” Word Task Pane. This type of task pane is not the same animal we know and love as Office developers. This type of is the kind a web developer-type would love.
The Translate Word Task Pane App
Today’s sample will integrate with the Microsoft Translator API. The app will allow you to select text within Word, translate the text, and view the translation results. Here’s the design:
To build this app, you need the following tools:
- Visual Studio 2012
- With Apps for Office project templates (Preview 2 is required)
- Word 2013
- A web site that can execute a .PHP file
Also, you need a Microsoft Translate Account and application. To get what you need for this code sample, complete these steps:
- Open your browser and navigate to the Windows Azure Marketplace. Go ahead and sign-in, etc.
- Search for “Translate” in the search box (upper right-hand corner).
- Click Microsoft Translate. In its detail page, select the plan you want. I chose the free one. After all, this is a sample application.
Now for the tricky part no one tells you about
This heading isn’t entirely true. Sure people tell you about it but they don’t do a good job explaining it. This what you do:
- Navigate to the Applications page of the Azure Marketplace. We need to create an app to use with Microsoft Translate.
- Click the Register button.
- In the Register your application page enter the following:
- ClientID = [Something Unique]
- Name=Translate
- Redirect URI = your web site’s URL
- Enable subdomain access = true
The URL needs to be to your site. It needs to be a site that can run .PHP.
- Click Create.
We now have an app we can use with Microsoft Translate. We will use the ClientID and Client Secret values to authenticate to Translate and create an authentication token.
Now, let’s open Visual Studio 2012 and build the Translate task pane app.
Creating the Translate Word App
The Translate app uses the Apps for Office 2013 Visual Studio 2012 template. If you don’t see this template in the New Project dialog box, be sure you installed all the pre-requisites listed above.
- In the Visual Studio menu, select File > New Project from the main menu.
- In the New Project dialog box, select the Templates > Office/SharePoint > Apps node.
- Click the App for Office 2013 project template and name the project Translate. Click the OK button.
- The Create App for Office dialog box will display. Select the Task pane app in option and only enable Word as a host Office application. Click Finish to create the project.
Visual Studio will do its thing and display the Translate.html file in the code window. We are ready to add the HTML for the task pane. Listing 1 contains the HTML. I will leave it to you how to add it to your app. If it were me (and clearly it is not me) I’d copy it and paste it.
Listing 1. The HTML for Translate.html
<div id="Content"> <div class="functons"> <div> <label>Lang from <select name="lang-from" disabled></select> </label> </div> <div> <label>Lang To <select name="lang-to" disabled></select> </label> </div> <div> <input type="button" disabled value="Translate" id="getDataBtn" style="padding: 0px; width: 100px;" /> </div> </div> <div class="translate-langs"> </div> <div class="translate-text"> </div> </div>
This task pane is a basic HTML page. It contains these important controls:
- Lang-from: a select control that allows the user to specify the language of origin. The default is “auto”.
- Lang-to: a select control that allows the user to specify the destination language.
- getDataBtn: The button that initiates the translation action.
- Translate-langs: A div that will display the languages involved in the translation.
- Translate-text: A div that will display the translated text.
Let’s take a look at the actual code… the JavaScript (as seen in Listing 2). While you look at it, open Translate.js and past the contents of Listing 2 into it.
Listing 2. The Translate.js JavaScript Goodness.
// This function is run when the app is ready to start interacting with the host application // It ensures the DOM is ready before adding click handlers to buttons Office.initialize = function (reason) { $(document).ready(function () { $('#getDataBtn').click(function () { App.getData('#selectedDataTxt'); }); $(window).bind('bingapi-langs-ready', App.ReadyLangs); }); }; //Object App = {}; // Reads the data from current selection of the document and displays it in a textbox App.getData = function (elementId) { Office.context.document.getSelectedDataAsync(Office.CoercionType.Text, function (result) { if (result.status === 'succeeded') { //$(elementId).val(result.value); ServiceTranslate.translationRequest(result.value, App.translateSuccess, App.translateFail) } }); } App.translateSuccess = function (data) { $(".translate-langs").html(langDict[data.from] + " -> " + langDict[App.getLangTo()]); $(".translate-text").html(data.text); } App.translateFail = function () { $(".translate-text").html("Something wrong... :("); } App.ReadyLangs = function (event) { App.FillSelectLang('lang-from', true, 'auto'); App.FillSelectLang('lang-to', false, 'en'); $(".functons *[disabled]").removeAttr("disabled"); } App.FillSelectLang = function (selectname, insertAuto, defaultSelect) { sel = $("select[name='" + selectname + "']"); langs = ServiceTranslate.availableLangs; if (insertAuto) { sel.append('<option value="auto" ' + ((defaultSelect == 'auto') ? "selected" : "") + '>' + langDict['auto'] + '</options>'); } for (lang in langs) { sel.append('<option value="' + langs[lang][0] + '" ' + ((defaultSelect == langs[lang][0]) ? "selected" : "") + '>' + langDict[langs[lang][0]] + '</options>'); } } App.getLangTo = function () { return $("select[name='lang-to']").val(); } App.getLangFrom = function () { return $("select[name='lang-from']").val(); }
If you are like me (and you probably are not but… ) then this stuff looks like a foreign language in need of its own translation service. Luckily, you have me and I get you part of the way there. Let’s translate what’s going on. We’ll take it method by method, event by event.
- Office.Initialize: This is the initialization event for the app. Here, we add a click event to the getDataBtn button. When clicked, this button calls App.GetData. The event finishes by filling the select controls by calling App.ReadyLangs.
- App.GetData: Reads the data from current selection of the document and displays it in a textbox. If successful, this function calls App.TranslateSuccess. If it fails, it calls App.translateFail.
- App.translateSuccess: Updates the translate-langs and translate-text div tags.
- App.translateFail: Updates the translate-text div tag with the ugly news of a failed translation.
- App.FillSelectLang: Fills the select fields with a listing of the supported languages.
- App.getLangTo: Returns the destination language. There is a call to Service.Translate. This method resides in a separate JavaScript file found in Listing 3. This code is akin to VooDoo. Victor wrote it and I’m still digging into it.
- App.getLangFrom: returns the origin language.
As I mentioned a second ago. We have a bit of VooDoo. Listing 3 contains the incantation. You need to add this to your project as a separate JavaScript file.
- 1. In the Visual Studio main menu, click Project | Add New Item
- In the Add New Item dialog box, select JavaScript file. Name the file ServiceTranslate.js.
- Click the Add button.
- Paste Listing 3’s content into the file.
Listing 3. Incantation #1 – ServiceListing.js
if ("undefined" === typeof (ServiceTranslate)) { langDict = { "auto": "Auto", "af": "Afrikaans", "sq": "Albanian", "ar": "Arabic", "be": "Belarusian", "bg": "Bulgarian", "ca": "Catalan", "zh-CHS": "Chinese (Simplified)", "zh-CHT": "Chinese (Traditional)", "hr": "Croatian", "cs": "Czech", "da": "Danish", "nl": "Dutch", "en": "English", "et": "Estonian", "tl": "Filipino", "fi": "Finnish", "fr": "French", "gl": "Galician", "de": "German", "el": "Greek", "iw": "Hebrew", "hi": "Hindi", "ht": "Haitian Creole", "he": "Hebrew", "hu": "Hungarian", "is": "Icelandic", "id": "Indonesian", "ga": "Irish", "it": "Italian", "ja": "Japanese", "ko": "Korean", "lv": "Latvian", "lt": "Lithuanian", "mk": "Macedonian", "ms": "Malay", "mww": "Hmong Daw", "mt": "Maltese", "no": "Norwegian", "fa": "Persian", "pl": "Polish", "pt": "Portuguese", "ro": "Romanian", "ru": "Russian", "sr": "Serbian", "sk": "Slovak", "sl": "Slovenian", "es": "Spanish", "sw": "Swahili", "sv": "Swedish", "th": "Thai", "tr": "Turkish", "uk": "Ukrainian", "vi": "Vietnamese", "cy": "Welsh", "yi": "Yiddish" }; var ServiceTranslate = { onLoadFn: null, onErrorFn: null, bingAppId: "", availableLangs: null, // languages langConf: { langDict: null }, init: function () { ServiceTranslate.getTokenAndLangs(); }, getTokenAndLangs: function () { var Url = "[YOUR URL GOES HERE]/getBingToken.php"; //$.support.cors = true; $.ajax({ url: Url, type: "GET", dataType: "script", crossDomain: true }).done(function (data) { return; }).fail(function (data) { return; }); }, readToken: function( data ){ if (typeof (data.access_token) != "undefined") { ServiceTranslate.bingAppId = "Bearer " + data.access_token; var event = jQuery.Event("bingapi-ready"); event.apiId = ServiceTranslate.bingAppId; $(window).trigger(event); } }, loadLangs: function ( data ){ ServiceTranslate.availableLangs = data; var event = jQuery.Event("bingapi-langs-ready"); event.langs = ServiceTranslate.availableLangs; $(window).trigger(event); }, translateSuccess: function (content) { var lang = null; var transalatedText = ""; for (rIndex in content) { lang = content[rIndex].From; transalatedText += content[rIndex].TranslatedText; } ServiceTranslate.onLoadFn({ from: lang, text: transalatedText }); return; }, translateFail: function (sender) { return; }, translationRequest: function (text, onLoadFn, onErrorFn) { ServiceTranslate.onLoadFn = onLoadFn; ServiceTranslate.onErrorFn = onErrorFn; langTo = App.getLangTo(); langFrom = App.getLangFrom(); var url = this.getBingUrlToTranslate(langFrom, langTo, text); request = $.ajax({ url: url, type: "GET", dataType: "script" }); }, getBingUrlToTranslate: function (langFrom, langTo, text) { langFrom = (langFrom == "auto") ? "" : langFrom; arrayOfLines = text.match(/[^\r\n]+/g); text = JSON.stringify(arrayOfLines); formattedUrl = 'https://api.microsofttranslator.com/v2/ajax.svc/TranslateArray?appId=%22' + encodeURIComponent(ServiceTranslate.bingAppId) + '%22&texts=' + encodeURIComponent(text) + '&from=%22' + langFrom + '%22&to=%22' + langTo + '%22&oncomplete=ServiceTranslate.translateSuccess&onerror=ServiceTranslate.translateFail'; return formattedUrl; }, // remove the whitespace from the beginning and end of a string (thanks jQuery ;-) trim: function (str) { return (str || "").replace(/^\s+|\s+$/g, ""); } }; (function () { }).apply(ServiceTranslate); } $(document).ready(function () { ServiceTranslate.init(); });
ServiceTranslate handles the calls to an additional set of spells that reside in a PHP-based website. Listing 4 contains the magic. You need to create this file and load it to your website. You then need to edit ServiceTranslate to point to your URL. Just replace “YOUR URL GOES HERE” with your URL. Then, upload the file to the URL and we are ready to test.
Listing 4. Incantation #2: getBingToken.php
<?php /* * Get the access token. * * @param string $grantType Grant type. * @param string $scopeUrl Application Scope URL. * @param string $clientID Application client ID. * @param string $clientSecret Application client ID. * @param string $authUrl Oauth Url. * * @return string. */ function getTokens($url, $authUrl){ try { //Initialize the Curl Session. $ch = curl_init(); //Create an Http Query.// $paramArr = http_build_query($url); //Set the Curl URL. curl_setopt($ch, CURLOPT_URL, $authUrl); //Set HTTP POST Request. curl_setopt($ch, CURLOPT_POST, TRUE); //Set data to POST in HTTP "POST" Operation. curl_setopt($ch, CURLOPT_POSTFIELDS, $paramArr); //CURLOPT_RETURNTRANSFER- TRUE to return the transfer as a string of the //return value of curl_exec(). curl_setopt ($ch, CURLOPT_RETURNTRANSFER, TRUE); //CURLOPT_SSL_VERIFYPEER- Set FALSE to stop cURL from verifying the //peer's certificate. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //Execute the cURL session. $strResponse = curl_exec($ch); //Get the Error Code returned by Curl. $curlErrno = curl_errno($ch); if($curlErrno){ $curlError = curl_error($ch); throw new Exception($curlError); } //Close the Curl Session. curl_close($ch); return $strResponse; } catch (Exception $e) { echo "Exception-".$e->getMessage(); } } function curlRequest($url, $authHeader, $postData=''){ //Initialize the Curl Session. $ch = curl_init(); //Set the Curl url. curl_setopt ($ch, CURLOPT_URL, $url); //Set the HTTP HEADER Fields. curl_setopt ($ch, CURLOPT_HTTPHEADER, array($authHeader,"Content-Type: text/xml")); //CURLOPT_RETURNTRANSFER- TRUE to return the transfer as a string of the //return value of curl_exec(). curl_setopt ($ch, CURLOPT_RETURNTRANSFER, TRUE); //CURLOPT_SSL_VERIFYPEER- Set FALSE to stop cURL from verifying the //peer's certificate. curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, False); if($postData) { //Set HTTP POST Request. curl_setopt($ch, CURLOPT_POST, TRUE); //Set data to POST in HTTP "POST" Operation. curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); } //Execute the cURL session. $curlResponse = curl_exec($ch); //Get the Error Code returned by Curl. $curlErrno = curl_errno($ch); if ($curlErrno) { $curlError = curl_error($ch); throw new Exception($curlError); } //Close a cURL session. curl_close($ch); return $curlResponse; } try { $makeUrl = array(); $makeUrl['grant_type'] = "client_credentials"; $makeUrl['scope'] = "https://api.microsofttranslator.com"; // TODO: replace the client_id with your ClientId $makeUrl['client_id'] = "602084b3-e68e-4be7-8cb3-01f969e73728"; // TODO: replace the client_secret with your ClientSecret $makeUrl['client_secret'] = "QhMXL02w4cA2jcxR/YyjDqIYQx7FpP9BbG0oKMyfvUQ="; //Create the AccessTokenAuthentication object. //Get the Access token. $authUrl = "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13/"; $accessToken = getTokens($makeUrl, $authUrl); //Create the authorization Header string. ?> ServiceTranslate.readToken(<?=$accessToken ?>); <? $jsonO = json_decode($accessToken); //Create the authorization Header string. $authHeader = "Authorization: Bearer ". $jsonO->access_token; //Call the Detect Method. $url = "https://api.microsofttranslator.com/V2/Http.svc/GetLanguagesForTranslate"; //Call Curl Request.// $strResponse = curlRequest($url, $authHeader); // Interprets a string of XML into an object. $xmlObj = simplexml_load_string($strResponse); $languageCodes = array(); foreach($xmlObj->string as $language){ $languageCodes[] = $language; } ?> ServiceTranslate.loadLangs(<?=json_encode($languageCodes) ?>); <? } catch (Exception $e) { echo "Exception: " . $e->getMessage() . PHP_EOL; } ?>
Translate Word app in action!
Press F5 and wait for Word to display. When it does, type “Hello World” into the Word document.
In the Translate task pane do the following:
- Leave Lang from set to “Auto”. I admit this actually qualifies as doing nothing.
- Select Russian in the Lang to field.
- Click the Translate button
Voila! You should see something like this:
Typically, I use this space to summarize the article and land the plane. Today, I’m skipping it because I want to thank Victor Varvanovich. He has mastered the Apps for Office model, took the idea, and wrote the code!
4 Comments
You made it seem too easy, thank you for sharing this translation app!
good info in this post
thank for your article.
Merci pour cet article .